#include "p-machine.hh"
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <string>
#include <vector>
#include <boost/format.hpp>
#ifndef NDEBUG
#include <iostream>
#endif

#define UINT16_MAX 0xffff

using std::cerr;
using std::cout;
using std::string;
using boost::format;

void print_objects(const Adventure& adv) {
  bool none = true;

  for(unsigned int i = 0; i < adv.objects.size(); ++i) {
    if(adv.objects[i].pos == adv.here) {
      if(none) {
	cout << "You can see:";
      }
      cout << (none ? " " : ", ")
	   << adv.get_object_name(i);
      none = false;
    }
  }
  if(!none) cout << ". ";
}

void print_exits(const Adventure& adv) {
  if(!adv.locations[adv.here].exits.empty()) {
    const ExitMap& exits = adv.locations[adv.here].exits;
    cout << "Exits are:";
    for(auto iter = exits.begin(); iter != exits.end(); ++iter) {
      auto name(adv.movement_verbs.find(iter->first));
      if(name == adv.movement_verbs.end()) {
	throw std::logic_error(str(format("no verb name for movement verb %d") % iter->first));
      }
      cout << (iter == exits.begin() ? " " : ", ");
      cout << name->second;
    }
    cout << ". ";
  }
}



pcode_array pcode_from_string(const string &s) {
  long l;
  pcode_array arr;
  std::istringstream iss(s);

  iss >> std::hex;
  while(iss >> l) {
    if((l > UINT16_MAX) || l < 0) throw std::invalid_argument("illegal value for opcode");
    arr.push_back(l & UINT16_MAX);
  }
  return arr;
}

int prun(Adventure &adv, const pcode_array &code, bool trace) {
  int x, y;
  bool end_event = false;
  struct {
    unsigned short op;
    unsigned int pc;
    std::stack<int> stack;
    string mkerr(const string& s) {
      return boost::str(format("Error at $%x opcode=$%04hX: %s") % pc % op % s);
    }
    std::runtime_error too_few_stack() { return std::runtime_error(mkerr("too few elements on stack")); }
    void check_min_elements(unsigned int min) {
      if(stack.size() < min) throw too_few_stack();
    }
  } L;

  if(trace) cerr << "pcode_array = " << &code << std::endl;
  for(L.pc = 0; L.pc < code.size() && !end_event; ++L.pc) {
    L.op = code.at(L.pc);
    if(trace) {
      cerr << format("pc=%04X op=%04hX") % L.pc % L.op;
      if(!L.stack.empty()) {
	cerr << format(" tos=%04x") % L.stack.top();
      }
      //std::copy(L.stack.begin(), L.stack.end(), std::ostream_iterator(cerr, ' '));
      cerr << std::endl;
    }
    switch(static_cast<pcode_opcodes>(L.op)) {
      //********************************************* general commands
    case pcode_nop: //NOP
      continue;
    case pcode_add:
      L.check_min_elements(2);
      x = L.stack.top();
      L.stack.pop();
      y = L.stack.top();
      L.stack.pop();
      L.stack.push(x + y);
      break;
      //	add	top two elements, result on stack
    case pcode_and: // and
      L.check_min_elements(2);
      x = L.stack.top();
      L.stack.pop();
      y = L.stack.top();
      L.stack.pop();
      L.stack.push(x && y);
      break;
      // $0003	branching
    case pcode_cmp_eq: // compare, cmp, equal
      L.check_min_elements(2);
      x = L.stack.top();
      L.stack.pop();
      y = L.stack.top();
      L.stack.pop();
      L.stack.push(x == y);
      break;
    case pcode_cmp_lt: // compare, cmp, equal
      L.check_min_elements(2);
      x = L.stack.top();
      L.stack.pop();
      y = L.stack.top();
      L.stack.pop();
      L.stack.push(x < y);
      break;
    case pcode_cmp_gt: // compare, cmp, equal
      L.check_min_elements(2);
      x = L.stack.top();
      L.stack.pop();
      y = L.stack.top();
      L.stack.pop();
      L.stack.push(x > y);
      break;
      // $0005	div
    case pcode_exchange: // exg
      L.check_min_elements(2);
      x = L.stack.top();
      L.stack.pop();
      y = L.stack.top();
      L.stack.pop();
      L.stack.push(x);
      L.stack.push(y);
      break;
      // $0007	mul
      // $0008	or
    case pcode_sub: //	sub
      L.check_min_elements(2);
      x = L.stack.top();
      L.stack.pop();
      y = L.stack.top();
      L.stack.pop();
      L.stack.push(y - x);
      break;
      // $000a	xor
      // load <where> <what>
    case pcode_dup: //dup
      L.check_min_elements(1);
      x = L.stack.top();
      L.stack.push(x);
      break;
    case pcode_dupn: //dup of n elements
      L.check_min_elements(1);
      x = L.stack.top();
      L.stack.pop();
      if(x <= 0)
	throw std::invalid_argument(L.mkerr("duplicate_n less then one element"));
      else {
	L.check_min_elements(x);
	std::vector<int> tmp(x);
	for(int i = 0; i < x; ++i) {
	  tmp[i] = L.stack.top();
	  L.stack.pop();
	}
	for(auto i = tmp.rbegin(); i != tmp.rend(); ++i) {
	  L.stack.push(*i);
	}
	for(auto i = tmp.rbegin(); i != tmp.rend(); ++i) {
	  L.stack.push(*i);
	}
      }
      break;
      //******************************************** game commands
    case pcode_look_long: //look, long description
      cout << adv.locations.at(adv.here).desc_long << ' ';
      break;
    case pcode_look_short: //look, short description
      cout << adv.locations.at(adv.here).desc_short << ' ';
      break;
    case pcode_write_mesg: //write message
      L.check_min_elements(1);
      y = L.stack.top();
      L.stack.pop();
      cout << adv.msgs.at(y) << ' ';
      break;
    case pcode_nl: //New line...
      cout << std::endl;
      break;
    case pcode_examine_obj: //examine object
      break;
    case pcode_move_obj: //move object to
      L.check_min_elements(2);
      y = L.stack.top();
      if((static_cast<unsigned int>(y) >= adv.objects.size()) || (y < 0))
	throw std::invalid_argument(L.mkerr("object does not exist"));
      L.stack.pop();
      x = L.stack.top();
      L.stack.pop();
      adv.objects.at(y).pos = x;
      break;
    case pcode_print_objs: //Print objects
      print_objects(adv);
      break;
    case pcode_print_exits: //print exits
      print_exits(adv);
      break;
    case pcode_inventory: //Inventory
      cout << adv.inventory();
      break;
      //******************************************** load
    case pcode_load_immediate: //load immediate
      L.stack.push(code.at(++L.pc));
      // stor <where> <what>
      // pop	destroy top element on stack
      // # "move"
      break;
    case pcode_load_false: //Load false
      L.stack.push(0);
      break;
    case pcode_load_true: //Load true
      L.stack.push(1);
      break;
    case pcode_load_here: //here
      L.stack.push(adv.here);
      break;
    case pcode_load_verb: //verb
      L.stack.push(adv.verb);
      break;
    case pcode_load_adverb: //adverb
      L.stack.push(adv.adverb);
      break;
    case pcode_load_obj1: //obj1
      L.stack.push(adv.obj1);
      break;
    case pcode_load_adj1: //adj1
      L.stack.push(adv.adj1);
      break;
    case pcode_load_obj2: //obj2
      L.stack.push(adv.obj2);
      break;
    case pcode_load_adj2: //adj2
      L.stack.push(adv.adj2);
      break;
    case pcode_load_counter: // Load counter
      L.check_min_elements(1);
      x = L.stack.top();
      if(x > 0x7fff) throw std::invalid_argument(L.mkerr("counter does not exist"));
      L.stack.pop();
      L.stack.push(adv.counters.at(x));
      break;
    case pcode_load_switch: // Load switch
      L.check_min_elements(1);
      x = L.stack.top();
      if(x > 0x7fff) throw std::invalid_argument(L.mkerr("switch does not exist"));
      L.stack.pop();
      L.stack.push(adv.switches.at(x));
      break;
    case pcode_load_visited: //Load if room has been visited...
      L.stack.push(adv.locations.at(adv.here).visited);
      break;
      //******************************************** store
    case pcode_store_here: //here
      L.check_min_elements(1);
      x = L.stack.top();
      adv.here = x;
      break;
    case pcode_store_verb: //verb
      L.check_min_elements(1);
      x = L.stack.top();
      L.stack.pop();
      adv.verb = x;
      break;
    case pcode_store_adverb: //adverb
      L.check_min_elements(1);
      x = L.stack.top();
      L.stack.pop();
      adv.adverb = x;
      break;
    case pcode_store_obj1: //obj1
      L.check_min_elements(1);
      x = L.stack.top();
      L.stack.pop();
      adv.obj1 = x;
      break;
    case pcode_store_adj1: //adj1
      L.check_min_elements(1);
      x = L.stack.top();
      L.stack.pop();
      adv.adj1 = x;
      break;
    case pcode_store_obj2: //obj2
      L.check_min_elements(1);
      x = L.stack.top();
      L.stack.pop();
      adv.obj2 = x;
      break;
    case pcode_store_adj2: //adj2
      L.check_min_elements(1);
      x = L.stack.top();
      L.stack.pop();
      adv.adj2 = x;
      break;
    case pcode_store_counter: //counter
      L.check_min_elements(2);
      y = L.stack.top();
      if((y > 0x7fff) || (y < 0))
	throw std::invalid_argument(L.mkerr("counter does not exist"));
      L.stack.pop();
      x = L.stack.top();
      L.stack.pop();
      adv.counters[y] = x;
      break;
    case pcode_store_switch: //switch
      L.check_min_elements(2);
      y = L.stack.top();
      if((y > 0x7fff) || (y < 0))
	throw std::invalid_argument(L.mkerr("switch does not exist"));
      L.stack.pop();
      x = L.stack.top();
      L.stack.pop();
      adv.switches.at(y) = x != 0;
      break;
      //******************************************** code flow
    case pcode_end_on_false: //end event on false
      L.check_min_elements(1);
      x = L.stack.top();
      L.stack.pop();
      if(x == 0) end_event = true;
      break;      
    case pcode_end_on_true: //end event on true
      L.check_min_elements(1);
      x = L.stack.top();
      L.stack.pop();
      if(x != 0) end_event = true;
      break;      
      //******************************************** illegal
    case 0x4a7c:
    case 0xffff:
    default:
      throw std::invalid_argument(str(format("illegal opcode $%04X") % L.op));
    }
  }
  if(!L.stack.empty()) {
    x = L.stack.top();
    if(trace) {
      cerr << "stack:";
      while(!L.stack.empty()) {
	cerr << ' ' << L.stack.top();
	L.stack.pop();
      }
      cerr << std::endl;
    }
  } else {
    x = -1;
  }
  if(x == 0) {
    return 0;
  } else if(x == 1) {
    return 1;
  }
  return -1;
}

